#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "./../sota/sota.h"
#include <windows.h>

EvilEye::EvilEye(QWidget * _parent) :
	QMainWindow( _parent),
	ui(new Ui::EvilEye)
{
	ui->setupUi(this);
	setObjectName("Evil_Eye_MainWindow");
	id = 0;
	prev_id = 0;
	sota::default_signal_ttl = 5000;
	sota::connect(
			("/" + objectName()).toAscii(),
			SIGNAL(picture_data(int, int, QByteArray, int, int)),
			this,
			SLOT(on_picture_data(int, int, QByteArray, int, int))
			);
	sota::connect(
			("/" + objectName()).toAscii(),
			SIGNAL(request_picture(int)),
			this,
			SLOT(on_request_picture(int))
			);
	sota::connect(
			("/" + objectName()).toAscii(),
			SIGNAL(remote_event(QVariantMap)),
			this,
			SLOT(on_remote_event(QVariantMap))
			);

	ui->scrollArea->setWidget(view = new QLabel());
	connect(this, SIGNAL(draw_pixmap(QPixmap)), view, SLOT(setPixmap(QPixmap)));
	view->installEventFilter(this);
	view->setFocusPolicy(Qt::StrongFocus);

	frames_count = 0;
	slow_timer = startTimer(2000);
}

EvilEye::~EvilEye()
{
	killTimer(slow_timer);
	delete ui;
}

void EvilEye::on_request_picture(int _pic_mode)
{
	qDebug() << "EvilEye::on_request_picture()";
	reconnectData();
	QTime _time_start = QTime::currentTime();
	if (_pic_mode != FullPicture)
	{
		prev_image = current_image;
	}
//	current_image = QPixmap::grabWindow(QApplication::desktop()->winId()).toImage();
	current_image = screen_shot.take();
	qDebug() << "Screenshot taking time is" << _time_start.msecsTo(QTime::currentTime());
	if (current_image.size() != prev_image.size())
	{
		_pic_mode = FullPicture;
	}
	switch (_pic_mode)
	{
	case FullPicture:
		{
			emit picture_data(
					_pic_mode,
					id = 0,
					QByteArray::fromRawData((char *)current_image.bits(), current_image.byteCount()),
					current_image.width(),
					current_image.height()
					);
			break;
		}
	case DiffPicture:
		{
			const int _i_size = current_image.byteCount() / sizeof(int);
			int * _diff = new int[2 * _i_size + 2]; // 2 * _i_size + 2 is maximum size
			const int * const _diff_start = _diff;
			const int * _c_ptr = (const int *)current_image.bits();
			const int * _p_ptr = (const int *)prev_image.bits();
			const int * const _c_ptr_end = _c_ptr + _i_size;
			* _diff++ = * _c_ptr++ - * _p_ptr++;
			* _diff = 1;

			// RLE compression
			QTime _time_start = QTime::currentTime();
			while (_c_ptr != _c_ptr_end)
			{
				if ((* (_diff + 1) = * _c_ptr++ - * _p_ptr++) != * (_diff - 1)) // difference between current and previous pixel
				{
					* (_diff += 2) = 1;
				}
				else
				{
					(* _diff)++; // incrementing counter
				}
			}
			_diff++;
			qDebug() << "RLE compression time is" << _time_start.msecsTo(QTime::currentTime());
			qDebug() << "EvilEye::on_request_picture(), DiffPicture size =" << (int)(_diff - _diff_start);
			// ping even if there's no changes
			emit picture_data(
					_pic_mode,
					++id,
					QByteArray::fromRawData((char *)_diff_start, ((int)(_diff - _diff_start)) * sizeof(int)),
					current_image.width(),
					current_image.height()
					);
			delete [] _diff_start;
			break;
		}
	}
}

void EvilEye::on_picture_data(int _pic_mode, int _id, QByteArray _data, int _w, int _h)
{
	frames_count++;
	qDebug() << "EvilEye::on_picture_data(), data =" << _data.size() / sizeof(int) << ", mode =" << _pic_mode << ", id =" << _id << ", size =" << QSize(_w, _h);
	switch (_pic_mode)
	{
	case DiffPicture:
		{
			if ((_id - prev_id > 1) || (view_image.size() != QSize(_w, _h))) // one or more packets were lost or size is wrong
			{
				qDebug() << "EvilEye::on_picture_data() DiffPicture failed, id =" << _id << ", prev_id =" << prev_id;
				prev_id = 0;
				emit request_picture(FullPicture);
				return;
			}
			int * _v_ptr = (int *)view_image.bits();
			const int * _d_ptr = (const int *)_data.constData();
			const int _d_size = _data.size() / sizeof(int);
			const int * const _d_ptr_end = _d_ptr + _d_size;
			// RLE decompression
			QTime _time_start = QTime::currentTime();
			while (_d_ptr != _d_ptr_end)
			{
				const int & _d_value = * _d_ptr++;
				const int & _d_count = * _d_ptr++;
				if (_d_value != 0)
				{
					const int * const _v_ptr_end = _v_ptr + _d_count;
					while (_v_ptr != _v_ptr_end)
					{
						* _v_ptr++ += _d_value;
					}
				}
				else
				{
					_v_ptr += _d_count;
				}
			}
			qDebug() << "RLE decompression time is" << _time_start.msecsTo(QTime::currentTime());
			qDebug() << "EvilEye::on_picture_data() DiffPicture success";
			break;
		}
	case FullPicture:
		{
			if ((int)(_w * _h * sizeof(int)) == _data.size())
			{
				view_image = QImage((const uchar *)_data.constData(), _w, _h, QImage::Format_RGB32).copy();
				qDebug() << "EvilEye::on_picture_data() FullPicture success";
			}
			else
			{
				qDebug() << "EvilEye::on_picture_data() FullPicture failed";
			}
			break;
		}
	}
	prev_id = _id;
	emit draw_pixmap(QPixmap::fromImage(view_image));
	emit request_picture(DiffPicture);
}

void EvilEye::on_actionExit_triggered()
{
	qDebug() << "EvilEye::on_actionExit_triggered()";
	QCoreApplication::instance()->quit();
}

void EvilEye::reconnectRequest()
{
	qDebug() << "EvilEye::reconnectRequest()";
	if (cur_address_request != ui->address->currentText())
	{
		sota::disconnect(this, SIGNAL(requestPicture(int)), 0, 0);
		sota::connect(
				this,
				SIGNAL(request_picture(int)),
				(ui->address->currentText() + "/" + objectName()).toAscii(),
				SLOT(on_request_picture(int))
				);

		sota::disconnect(this, SIGNAL(remote_event(QVariantMap)), 0, 0);
		sota::connect(
				this,
				SIGNAL(remote_event(QVariantMap)),
				(ui->address->currentText() + "/" + objectName()).toAscii(),
				SLOT(on_remote_event(QVariantMap))
				);

		if (ui->address->findText(cur_address_request = ui->address->currentText()) == -1)
		{
			ui->address->addItem(cur_address_request);
		}
	}
}

void EvilEye::reconnectData()
{
	qDebug() << "EvilEye::reconnectData()";
	if (cur_address_data != sota::caller_address())
	{
		sota::disconnect(this, SIGNAL(picture_data(int, int, QByteArray, int, int)), 0, 0);
		sota::connect(
				this,
				SIGNAL(picture_data(int, int, QByteArray, int, int)),
				((cur_address_data = sota::caller_address()) + "/" + objectName()).toAscii(),
				SLOT(on_picture_data(int, int, QByteArray, int, int))
				);
		qDebug() << "EvilEye::reconnectData(), cur_address_data =" << cur_address_data;
	}
}

void EvilEye::on_watch_clicked()
{
	qDebug() << "EvilEye::on_connect_clicked()";
	reconnectRequest();
	emit request_picture(FullPicture);
}

void EvilEye::timerEvent(QTimerEvent * _te)
{
	if (_te->timerId() == slow_timer)
	{
		QTime _cur_time = QTime::currentTime();
		int _delta = (prev_time.msecsTo(_cur_time) + 1);
		ui->fps->setText("FPS: " + QString::number(frames_count * 1000.0 / _delta, '.', 1));
		frames_count = 0;
		prev_time = _cur_time;
	}
}

bool EvilEye::eventFilter(QObject * _obj, QEvent * _ev)
{
	if (_obj == view)
	{
		switch ((int)_ev->type())
		{
			case QEvent::MouseButtonPress:
			case QEvent::MouseButtonRelease:
			case QEvent::MouseMove:
			case QEvent::MouseButtonDblClick:
			{
				emit remote_event(serializeMouseEvent((QMouseEvent *)_ev));
				return true;
			}
			case QEvent::KeyPress:
			case QEvent::KeyRelease:
			{
				emit remote_event(serializeKeyboardEvent((QKeyEvent *)_ev));
				return true;
			}
		}
	}
	return QMainWindow::eventFilter(_obj, _ev);
}


QVariantMap EvilEye::serializeMouseEvent(QMouseEvent * _me)
{
	QVariantMap _map;
	_map["type"] = _me->type();
	_map["button"] = _me->button();
	_map["pos"] = _me->pos();
	_map["modifiers"] = (int)_me->modifiers();
	return _map;
}

QVariantMap EvilEye::serializeKeyboardEvent(QKeyEvent * _ke)
{
	QVariantMap _map;
	_map["type"] = _ke->type();
	_map["key"] = _ke->key();
	_map["text"] = _ke->text();
	_map["modifiers"] = (int)_ke->modifiers();
	_map["nativeScanCode"] = _ke->nativeScanCode();
	_map["nativeVirtualKey"] = _ke->nativeVirtualKey();
	_map["nativeModifiers"] = _ke->nativeModifiers();
	return _map;
}

void EvilEye::on_remote_event(QVariantMap _map)
{
	int _type = _map["type"].toInt();
	if ((_type >= QEvent::MouseButtonPress) && (_type <= QEvent::MouseMove))
	{
		QCursor::setPos(_map["pos"].toPoint());
		if (_type != QEvent::MouseMove)
		{
			DWORD _mflag = 0;
			switch (_type)
			{
				case QEvent::MouseButtonPress:
				{
					switch (_map["button"].toInt())
					{
						case Qt::LeftButton: _mflag |= MOUSEEVENTF_LEFTDOWN; break;
						case Qt::RightButton: _mflag |= MOUSEEVENTF_RIGHTDOWN; break;
						case Qt::MidButton: _mflag |= MOUSEEVENTF_MIDDLEDOWN; break;
					}
					break;
				}
				case QEvent::MouseButtonRelease:
				{
					switch (_map["button"].toInt())
					{
						case Qt::LeftButton: _mflag |= MOUSEEVENTF_LEFTUP; break;
						case Qt::RightButton: _mflag |= MOUSEEVENTF_RIGHTUP; break;
						case Qt::MidButton: _mflag |= MOUSEEVENTF_MIDDLEUP; break;
					}
					break;
				}
			}
			::mouse_event(_mflag, 0, 0, 0, ::GetMessageExtraInfo());
			if (_type == QEvent::MouseButtonDblClick)
			{
				::Sleep(100);
				::mouse_event(_mflag, 0, 0, 0, ::GetMessageExtraInfo());
			}
		}
	}
	if ((_type == QEvent::KeyPress) || (_type == QEvent::KeyRelease))
	{
		::keybd_event(
				_map["nativeVirtualKey"].toInt(),
				_map["nativeScanCode"].toInt(),
				(_type == QEvent::KeyRelease) ? (KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP) : KEYEVENTF_EXTENDEDKEY,
				::GetMessageExtraInfo());
	}
}
